#ifndef TRAINING_LEVEL2_H
#define TRAINING_LEVEL2__H

#include "common_includes.h"

#include "work_result.h"
#include "training_level1.h"

template<typename T, typename Music_T>
class TrainingLevel2{ //Todo, copy or move?
private:
	thread thread_;
	promise<WorkResult<T>> promise_;
	mutex mutex_;
	condition_variable condition_variable_;bool destruction;

	vector<reference_wrapper<Music<Music_T>>> musics;
	const Transitions<T>* transitions;
	Music_T best_state_bias;

	vector<array<T, NUM_STATES>> forward_backward;
	vector<array<T, NUM_STATES>> forward_backward_temp;

	vector<shared_ptr<TrainingLevel1<T,Music_T>>> training_level1s;
	vector<future<WorkResult<T>>> training_level1_futures;
	deque<bool> training_level1_is_working; //TODO since vector bool is bad
public:
	TrainingLevel2(unsigned num_sub_trainers = thread::hardware_concurrency()){
		destruction = false;
		num_sub_trainers += (num_sub_trainers==0);
		training_level1s.reserve(num_sub_trainers);
		for(unsigned i=0; i<num_sub_trainers; ++i){
			training_level1s.push_back(shared_ptr<TrainingLevel1<T,Music_T>>(new TrainingLevel1<T,Music_T>()));
		}
		training_level1_futures.resize(num_sub_trainers);
		training_level1_is_working.resize(num_sub_trainers, false);
		thread_ = thread(&TrainingLevel2::working_thread, this);
	}

	~TrainingLevel2(){
		{
			lock_guard<mutex> lock(mutex_);
			destruction = true;
			condition_variable_.notify_one();
		}
		thread_.join();
	}

	TrainingLevel2(const TrainingLevel2& tl1) = delete;

	WorkResult<T> evaluate(const vector<reference_wrapper<Music<Music_T>>>& musics, const Transitions<T>& transitions, Music_T best_state_bias){
		WorkResult<T> work_result;
		work_result.clear();

		unsigned music_next_index = 0;
		unsigned num_running_training_level1 = 0;

		shared_ptr<mutex> mutex_level1 = shared_ptr<mutex>(new mutex());
		shared_ptr<condition_variable> condition_variable_level1 = shared_ptr<condition_variable>(new condition_variable());
		unique_lock<mutex> lock_level1(*mutex_level1);

		do{
			if (num_running_training_level1 == training_level1s.size() || music_next_index == musics.size()){
				condition_variable_level1->wait(lock_level1);
			}
			for(unsigned i=0; i < training_level1_futures.size(); ++i){
				if (training_level1_futures[i].valid() && (future_status)training_level1_futures[i].wait_for(chrono::seconds(0)) != future_status::ready){ // Todo, not only valid, the value should be ready.
					work_result += training_level1_futures[i].get();
					--num_running_training_level1;
				}

				if (music_next_index < musics.size() &&
						!training_level1_futures[i].valid())
				{
					training_level1_futures[i] = training_level1s[i]->async_evaluate({musics[music_next_index]}, transitions, best_state_bias, mutex_level1, condition_variable_level1);
					++music_next_index;
					++num_running_training_level1;

				}
			}

		}while(num_running_training_level1 > 0);

//		training_level1_is_working.assign(training_level1_is_working.size(),false);
		return work_result;
	}

	future<WorkResult<T>> async_evaluate(vector<reference_wrapper<Music<Music_T>>> musics, const Transitions<T>& transitions, Music_T best_state_bias){ // TODO const for the first argument?
		lock_guard<std::mutex> lock(mutex_);
		promise_ = promise<WorkResult<T>>();
		//run thread
		this->musics = musics;
		this->transitions = &transitions;
		this->best_state_bias = best_state_bias;
		condition_variable_.notify_one();
		return promise_.get_future();
	}

	void working_thread(){
		unique_lock<mutex> lock(mutex_);
		while (true){
			condition_variable_.wait(lock);
			if (destruction){
				break;
			}
			promise_.set_value(evaluate(musics, *transitions, best_state_bias));
		}
	}
};

#endif

